<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <!-- hexo-inject:begin --><!-- hexo-inject:end --><meta charset="utf-8">
    <meta name="keywords" content="Vue源码系列3-Vue的异步更新机制, HTML,CSS,JavaScript,JQuery,React,Vue.js等">
    <meta name="description" content="别做一个Vue api调用师">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <!-- Global site tag (gtag.js) - Google Analytics -->


    <title>Vue源码系列3-Vue的异步更新机制 | zihao blog</title>
    <link rel="icon" type="image/png" href="/pride-blog/favicon.png">

    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/awesome/css/all.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/css/matery.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/css/my.css">

    <script src="/pride-blog/libs/jquery/jquery.min.js"></script>

<meta name="generator" content="Hexo 5.3.0"><link rel="stylesheet" href="/pride-blog/css/prism-tomorrow.css" type="text/css"><!-- hexo-inject:begin --><!-- hexo-inject:end --></head>




<body>
    <!-- hexo-inject:begin --><!-- hexo-inject:end --><header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/pride-blog/" class="waves-effect waves-light">
                    
                    <img src="/pride-blog/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">zihao blog</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/pride-blog/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/pride-blog/categories/CSS/" class="waves-effect waves-light">
      
      <i class="fas fa-bookmark" style="zoom: 0.6;"></i>
      
      <span>分类</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/pride-blog/archives" class="waves-effect waves-light">
      
      <i class="fas fa-archive" style="zoom: 0.6;"></i>
      
      <span>归档</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>


<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="/pride-blog/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">zihao blog</div>
        <div class="logo-desc">
            
            别做一个Vue api调用师
            
        </div>
    </div>

    

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/pride-blog/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/pride-blog/categories/CSS/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-bookmark"></i>
			
			分类
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/pride-blog/archives" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-archive"></i>
			
			归档
		</a>
          
        </li>
        
        
    </ul>
</div>


        </div>

        
    </nav>

</header>

    



<div class="bg-cover pd-header post-cover" style="background-image: url('/pride-blog/medias/featureimages/18.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 class="description center-align post-title">Vue源码系列3-Vue的异步更新机制</h1>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="/pride-blog/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        width: 345px;
        padding-left: 20px;
    }

    .toc-widget .toc-title {
        padding: 35px 0 15px 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content {
        padding-bottom: 30px;
        overflow: auto;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;

        position: absolute;
        right: 23.5vw;
        display: block;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                    <div class="article-tag">
                        
                            <a href="/pride-blog/tags/Vue%E6%BA%90%E7%A0%81/">
                                <span class="chip bg-color">Vue源码</span>
                            </a>
                        
                    </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/pride-blog/categories/Vue%E6%BA%90%E7%A0%81/" class="post-category">
                                Vue源码
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2021-06-17
                </div>
                

                

                

                

                
            </div>
        </div>
        <hr class="clearfix">

        

        

        <div class="card-content article-card-content">
            <div id="articleContent">
                <h3 id="Vue-源码系列-3-异步更新"><a href="#Vue-源码系列-3-异步更新" class="headerlink" title="Vue 源码系列 3 异步更新"></a>Vue 源码系列 3 异步更新</h3><p>目的:</p>
<ul>
<li>1.0 深入了解 Vue 的异步更新机制</li>
<li>2.0 nextTick 的原理</li>
</ul>
<h4 id="Vue-的异步更新机制"><a href="#Vue-的异步更新机制" class="headerlink" title="Vue 的异步更新机制"></a>Vue 的异步更新机制</h4><p>学习了 Vue 的响应式原理之后,就知道 Vue 是通过 Dep 进行依赖收集,并调用 notify()通知 watcher 进行依赖更新;所以 Vue 的异步更新机制的入口也在 Dep.notify();</p>
<h4 id="1-0-Dep-notify-函数"><a href="#1-0-Dep-notify-函数" class="headerlink" title="1.0 Dep.notify()函数"></a>1.0 Dep.notify()函数</h4><p><code>目录:/src/core/observer/dep.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">  notify() &#123;
    // stabilize the subscriber list first
    // 首先缓存所有的waychers
    const subs = this.subs.slice();
    if (process.env.NODE_ENV !== "production" && !config.async) &#123;
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id);
    &#125;
    // 遍历所有的watchers
    for (let i = 0, l = subs.length; i < l; i++) &#123;
      // 调用每一个watchers的update()
      subs[i].update();
    &#125;
  &#125;</code></pre>
<p>Dep.notify()函数做了什么？</p>
<ul>
<li>1.0 首先遍历所有的 watchers;</li>
<li>2.0 将 watcher 有序排列;</li>
<li>3.0 触发每一个 watcher 的 update()进行数据更新;</li>
</ul>
<h4 id="2-0-watcher-update"><a href="#2-0-watcher-update" class="headerlink" title="2.0 watcher.update()"></a>2.0 watcher.update()</h4><p><code>目录:/src/core/observer/watcher.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">  update() &#123;
  /* istanbul ignore else */
  // 如果是懒执行 ---computed
  if (this.lazy) &#123;
    // 重新将dirty属性置为true
    this.dirty = true;
  &#125; else if (this.sync) &#123;
    this.run();
  &#125; else &#123;
    // 将当前的watcher放入watcher队列
    queueWatcher(this);
  &#125;
&#125;</code></pre>
<p>watcher.update()做了什么？</p>
<ul>
<li>1.0 如果是懒执行,则将 dirty 属性重新置为 true ==&gt;特指 computedWatchers;</li>
<li>2.0 大部分是调用 queueWatcher 方法,将当前的 watchers 加入到 watchers 队列中;</li>
</ul>
<h4 id="3-0-watcher-queueWatcher"><a href="#3-0-watcher-queueWatcher" class="headerlink" title="3.0 watcher.queueWatcher()"></a>3.0 watcher.queueWatcher()</h4><p><code>目录:/src/core/observer/scheduler.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">export function queueWatcher (watcher: Watcher) &#123;
  const id = watcher.id
  // 判重处理 如果watcher存在,则跳过,否则加入watcher队列
  if (has[id] == null) &#123;
    // 缓存watcher id,用于判断watcher是否已经入队
    has[id] = true
    if (!flushing) &#123;
      // 当前没有处于刷新队列状态,watcher则直接入队列
      queue.push(watcher)
    &#125; else &#123;
      // 已经在watcher队列中的，倒序遍历;
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) &#123;
        i--
      &#125;
      // 将当前的watcher放入已在排序的队列中,切队列是有序的
      queue.splice(i + 1, 0, watcher)
    &#125;
    // queue the flush
    if (!waiting) &#123;
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) &#123;
          // 直接刷新调度队列
        flushSchedulerQueue()
        return
      &#125;
    //   将回调函数(flushSchedulerQueue)放入callbacks数组;
    // 通过pending控制向浏览器任务队列中添加flushCallbacks函数
      nextTick(flushSchedulerQueue)
    &#125;
  &#125;
&#125;
</code></pre>
<p>watcher.queueWatcher()做了什么？</p>
<ul>
<li>1.0 对 watcher 进行判重处理,如果存在就跳过,否则加入 watcher 队列;</li>
<li>2.0 已在 watcher 队列中,则倒序遍历;将当前的 watcher 放入已在排序的队列中,切记队列是有序的;</li>
<li>3.0 调用$nextTick();</li>
</ul>
<h4 id="3-0-nextTick"><a href="#3-0-nextTick" class="headerlink" title="3.0 nextTick()"></a>3.0 nextTick()</h4><p><code>目录:/src/core/util/next-tick.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">export function nextTick (cb?: Function, ctx?: Object) &#123;
  let _resolve
  // 用callback 数组存储经过包装的cb函数
  callbacks.push(() => &#123;
    if (cb) &#123;
      try &#123;
        cb.call(ctx)
      &#125; catch (e) &#123;
        handleError(e, ctx, 'nextTick')
      &#125;
    &#125; else if (_resolve) &#123;
      _resolve(ctx)
    &#125;
  &#125;)
  if (!pending) &#123;
    pending = true
    // 执行timeFunc,在浏览器的任务队列中,(首选微任务队列)放入flushCallbacks函数
    timerFunc()
  &#125;
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') &#123;
    return new Promise(resolve => &#123;
      _resolve = resolve
    &#125;)
  &#125;
&#125;
</code></pre>
<p>nextTick()做了什么？</p>
<ul>
<li>1.0 将回调函数用 try catch 进行包装处理</li>
<li>2.0 执行 timeFunc,在浏览器的任务队列中,(首选微任务队列)放入 flushCallbacks 函数</li>
</ul>
<h4 id="4-0-timerFunc"><a href="#4-0-timerFunc" class="headerlink" title="4.0 timerFunc()"></a>4.0 timerFunc()</h4><p><code>目录:/src/core/util/next-tick.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">// 可以看到 timerFunc 的作用很简单，就是将 flushCallbacks 函数放入浏览器的异步任务队列中
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) &#123;
  const p = Promise.resolve()
  // 首选 Promise.resolve().then()
  timerFunc = () => &#123;
    // 在 微任务队列 中放入 flushCallbacks 函数
    p.then(flushCallbacks)
    /**
     * 在有问题的UIWebViews中，Promise.then不会完全中断，但是它可能会陷入怪异的状态，
     * 在这种状态下，回调被推入微任务队列，但队列没有被刷新，直到浏览器需要执行其他工作，例如处理一个计时器。
     * 因此，我们可以通过添加空计时器来“强制”刷新微任务队列。
     */
    if (isIOS) setTimeout(noop)
  &#125;
  isUsingMicroTask = true
&#125; else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) &#123;
  // MutationObserver 次之
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, &#123;
    characterData: true
  &#125;)
  timerFunc = () => &#123;
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  &#125;
  isUsingMicroTask = true
&#125; else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) &#123;
  // 再就是 setImmediate，它其实已经是一个宏任务了，但仍然比 setTimeout 要好
  timerFunc = () => &#123;
    setImmediate(flushCallbacks)
  &#125;
&#125; else &#123;
  // 最后没办法，则使用 setTimeout
  timerFunc = () => &#123;
    setTimeout(flushCallbacks, 0)
  &#125;
&#125;</code></pre>
<p>timerFunc 函数做了什么？</p>
<ul>
<li>1.0 就是将 flushCallbacks 函数放入浏览器的异步任务队列中,首选微任务队列(Promise.then 方法),如果没有 promise.then 方法;则用 MutationObserver.observe 方法;还是没有的话就到宏任务队列中 setImmediate 和 setTimeout;(<strong>这里是一个优雅降级,对兼容性的处理</strong>)</li>
</ul>
<h4 id="5-0-flushCallbacks-函数"><a href="#5-0-flushCallbacks-函数" class="headerlink" title="5.0 flushCallbacks 函数"></a>5.0 flushCallbacks 函数</h4><p><code>目录:/src/core/util/next-tick.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">const callbacks = []
let pending = false
/**
 * 做了三件事：
 *   1、将 pending 置为 false
 *   2、清空 callbacks 数组
 *   3、执行 callbacks 数组中的每一个函数（比如 flushSchedulerQueue、用户调用 nextTick 传递的回调函数）
 */
function flushCallbacks () &#123;
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  // 遍历 callbacks 数组，执行其中存储的每个 flushSchedulerQueue 函数
  for (let i = 0; i < copies.length; i++) &#123;
    copies[i]()
  &#125;
&#125;

</code></pre>
<p>flushCallbacks 函数做了那些事？</p>
<ul>
<li>1.将 pending 置为 false;</li>
<li>2.清空 callbacks 数组,</li>
<li>3.清执行 callbacks 数组中的每一个函数,比如 flushSchedulerQueue、nextTick,</li>
</ul>
<h4 id="6-0-flushSchedulerQueue-函数"><a href="#6-0-flushSchedulerQueue-函数" class="headerlink" title="6.0 flushSchedulerQueue 函数"></a>6.0 flushSchedulerQueue 函数</h4><p><code>目录:/src/core/observer/scheduler.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">/**
 * Flush both queues and run the watchers.
 * 刷新队列，由 flushCallbacks 函数负责调用，主要做了如下两件事：
 *   1、更新 flushing 为 ture，表示正在刷新队列，在此期间往队列中 push 新的 watcher 时需要特殊处理（将其放在队列的合适位置）
 *   2、按照队列中的 watcher.id 从小到大排序，保证先创建的 watcher 先执行，也配合 第一步
 *   3、遍历 watcher 队列，依次执行 watcher.before、watcher.run，并清除缓存的 watcher
 */
function flushSchedulerQueue () &#123;
  currentFlushTimestamp = getNow()
  // 标志现在正在刷新队列
  flushing = true
  let watcher, id

  /**
   * 刷新队列之前先给队列排序（升序），可以保证：
   *   1、组件的更新顺序为从父级到子级，因为父组件总是在子组件之前被创建
   *   2、一个组件的用户 watcher 在其渲染 watcher 之前被执行，因为用户 watcher 先于 渲染 watcher 创建
   *   3、如果一个组件在其父组件的 watcher 执行期间被销毁，则它的 watcher 可以被跳过
   * 排序以后在刷新队列期间新进来的 watcher 也会按顺序放入队列的合适位置
   */
  queue.sort((a, b) => a.id - b.id)

  // 这里直接使用了 queue.length，动态计算队列的长度，没有缓存长度，是因为在执行现有 watcher 期间队列中可能会被 push 进新的 watcher
  for (index = 0; index < queue.length; index++) &#123;
    watcher = queue[index]
    // 执行 before 钩子，在使用 vm.$watch 或者 watch 选项时可以通过配置项（options.before）传递
    if (watcher.before) &#123;
      watcher.before()
    &#125;
    // 将缓存的 watcher 清除
    id = watcher.id
    has[id] = null

    // 执行 watcher.run，最终触发更新函数，比如 updateComponent 或者 获取 this.xx（xx 为用户 watch 的第二个参数），当然第二个参数也有可能是一个函数，那就直接执行
    watcher.run()
  &#125;

  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  /**
   * 重置调度状态：
   *   1、重置 has 缓存对象，has = &#123;&#125;
   *   2、waiting = flushing = false，表示刷新队列结束
   *     waiting = flushing = false，表示可以像 callbacks 数组中放入新的 flushSchedulerQueue 函数，并且可以向浏览器的任务队列放入下一个 flushCallbacks 函数了
   */
  resetSchedulerState()

  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) &#123;
    devtools.emit('flush')
  &#125;
&#125;

/**
 * Reset the scheduler's state.
 */
function resetSchedulerState () &#123;
  index = queue.length = activatedChildren.length = 0
  has = &#123;&#125;
  if (process.env.NODE_ENV !== 'production') &#123;
    circular = &#123;&#125;
  &#125;
  waiting = flushing = false
&#125;

</code></pre>
<p>flushSchedulerQueue 函数做了那些事情？</p>
<ul>
<li>刷新队列，由 flushCallbacks 函数负责调用，主要做了如下两件事：</li>
<li>1、更新 flushing 为 ture，表示正在刷新队列，在此期间往队列中 push 新的 watcher 时需要特殊处理（将其放在队列的合适位置）</li>
<li>2、按照队列中的 watcher.id 从小到大排序，保证先创建的 watcher 先执行，也配合 第一步</li>
<li>3、遍历 watcher 队列，依次执行 watcher.before、watcher.run，并清除缓存的 watcher</li>
</ul>
<h4 id="7-0-watcher-run"><a href="#7-0-watcher-run" class="headerlink" title="7.0 watcher.run()"></a>7.0 watcher.run()</h4><p><code>目录:/src/core/observer/watcher.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">/**
 * 由 刷新队列函数 flushSchedulerQueue 调用，如果是同步 watch，则由 this.update 直接调用，完成如下几件事：
 *   1、执行实例化 watcher 传递的第二个参数，updateComponent 或者 获取 this.xx 的一个函数(parsePath 返回的函数)
 *   2、更新旧值为新值
 *   3、执行实例化 watcher 时传递的第三个参数，比如用户 watcher 的回调函数
 */
run () &#123;
  if (this.active) &#123;
    // 调用 this.get 方法
    const value = this.get()
    if (
      value !== this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) &#123;
      // 更新旧值为新值
      const oldValue = this.value
      this.value = value

      if (this.user) &#123;
        // 如果是用户 watcher，则执行用户传递的第三个参数 —— 回调函数，参数为 val 和 oldVal
        try &#123;
          this.cb.call(this.vm, value, oldValue)
        &#125; catch (e) &#123;
          handleError(e, this.vm, `callback for watcher "$&#123;this.expression&#125;"`)
        &#125;
      &#125; else &#123;
        // 渲染 watcher，this.cb = noop，一个空函数
        this.cb.call(this.vm, value, oldValue)
      &#125;
    &#125;
  &#125;
&#125;

</code></pre>
<p>watcher.run()函数做了哪些事？</p>
<ul>
<li>由 刷新队列函数 flushSchedulerQueue 调用，如果是同步 watch，则由 this.update 直接调用，完成如下几件事：</li>
<li>1、执行实例化 watcher 传递的第二个参数，updateComponent 或者 获取 this.xx 的一个函数(parsePath 返回的函数)</li>
<li>2、更新旧值为新值</li>
<li>3、执行实例化 watcher 时传递的第三个参数，比如用户 watcher 的回调函数</li>
</ul>
<h4 id="8-0-watcher-get-函数"><a href="#8-0-watcher-get-函数" class="headerlink" title="8.0 watcher.get()函数"></a>8.0 watcher.get()函数</h4><p><code>目录:/src/core/observer/watcher.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">  /**
   * 执行 this.getter，并重新收集依赖
   * this.getter 是实例化 watcher 时传递的第二个参数，一个函数或者字符串，比如：updateComponent 或者 parsePath 返回的函数
   * 为什么要重新收集依赖？
   *   因为触发更新说明有响应式数据被更新了，但是被更新的数据虽然已经经过 observe 观察了，但是却没有进行依赖收集，
   *   所以，在更新页面时，会重新执行一次 render 函数，执行期间会触发读取操作，这时候进行依赖收集
   */
  get () &#123;
    // 打开 Dep.target，Dep.target = this
    pushTarget(this)
    // value 为回调函数执行的结果
    let value
    const vm = this.vm
    try &#123;
      // 执行回调函数，比如 updateComponent，进入 patch 阶段
      value = this.getter.call(vm, vm)
    &#125; catch (e) &#123;
      if (this.user) &#123;
        handleError(e, vm, `getter for watcher "$&#123;this.expression&#125;"`)
      &#125; else &#123;
        throw e
      &#125;
    &#125; finally &#123;
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) &#123;
        traverse(value)
      &#125;
      // 关闭 Dep.target，Dep.target = null
      popTarget()
      this.cleanupDeps()
    &#125;
    return value
  &#125;

</code></pre>
<p>watch.get()函数做了那些事情？</p>
<ul>
<li>执行 this.getter，并重新收集依赖</li>
<li>this.getter 是实例化 watcher 时传递的第二个参数，一个函数或者字符串，比如：updateComponent 或者 parsePath 返回的函数</li>
<li>为什么要重新收集依赖？</li>
<li>因为触发更新说明有响应式数据被更新了，但是被更新的数据虽然已经经过 observe 观察了，但是却没有进行依赖收集，</li>
<li>所以，在更新页面时，会重新执行一次 render 函数，执行期间会触发读取操作，这时候进行依赖收集</li>
</ul>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><ul>
<li><p>1.0 Vue 的异步更新机制是如何实现的？<br>Vue 的异步更新机制的核心是利用了浏览器的异步任务队列来实现的，首选微任务队列，宏任务队列次之。<br>当响应式数据更新后，会调用 dep.notify 方法，通知 dep 中收集的 watcher 去执行 update 方法，watcher.update 将 watcher 自己放入一个 watcher 队列（全局的 queue 数组）。<br>然后通过 nextTick 方法将一个刷新 watcher 队列的方法（flushSchedulerQueue）放入一个全局的 callbacks 数组中。<br>如果此时浏览器的异步任务队列中没有一个叫 flushCallbacks 的函数，则执行 timerFunc 函数，将 flushCallbacks 函数放入异步任务队列。如果异步任务队列中已经存在 flushCallbacks 函数，等待其执行完成以后再放入下一个 flushCallbacks 函数。<br>flushCallbacks 函数负责执行 callbacks 数组中的所有 flushSchedulerQueue 函数。<br>flushSchedulerQueue 函数负责刷新 watcher 队列，即执行 queue 数组中每一个 watcher 的 run 方法，从而进入更新阶段，比如执行组件更新函数或者执行用户 watch 的回调函数。</p>
</li>
<li><p>2.0 Vue 的 nextTick API 是如何实现的？</p>
<p>Vue.nextTick 或者 vm.$nextTick 的原理其实很简单，就做了两件事：<br>将传递的回调函数用 try catch 包裹然后放入 callbacks 数组;<br>执行 timerFunc 函数，在浏览器的异步任务队列放入一个刷新 callbacks 数组的函数;</p>
</li>
</ul>
<h4 id="2021-8-3-补充"><a href="#2021-8-3-补充" class="headerlink" title="2021.8.3 补充"></a>2021.8.3 补充</h4><h4 id="Vue-的异步更新机制是如何实现的？"><a href="#Vue-的异步更新机制是如何实现的？" class="headerlink" title="Vue 的异步更新机制是如何实现的？"></a>Vue 的异步更新机制是如何实现的？</h4><ul>
<li>Dep.notify()通知 watchers 进行数据更新;</li>
<li>watchers.update()将每一个 watchers 加入到更新的 watchers 更新队列中;</li>
<li>watcher.queueWatcher()将 watcher 进行遍历,然后再将 watcher 进行排序,调用 nextTick 方法;</li>
<li>nextTick()将一个刷新 watcher 队列的方法(flushSchedulerQueue)放入到一个全局的 callbacks 数组中;</li>
<li>timerFunc()将函数放入到浏览器中的任务队列中,优先放入微任务队列,没有微任务队列再放入宏任务队列(优雅降级)</li>
</ul>
<h4 id="Vue-的-nextTick-API-的实现原理？"><a href="#Vue-的-nextTick-API-的实现原理？" class="headerlink" title="Vue 的 nextTick API 的实现原理？"></a>Vue 的 nextTick API 的实现原理？</h4><p>nextTick:<code>在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法，获取更新后的 DOM。</code></p>
<ul>
<li>将传递的回调函数用 try catch 包装一下再放入 callback 数组中;</li>
<li>执行 timeFunc 函数,将 flushCallbacks(刷新的 callback 数组的函数)放入到浏览器的异步任务队列中;</li>
</ul>

                
            </div>
            <hr/>

            



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            
                                <a href="/pride-blog/tags/Vue%E6%BA%90%E7%A0%81/">
                                    <span class="chip bg-color">Vue源码</span>
                                </a>
                            
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/share/css/share.min.css">
<div id="article-share">

    

    

</div>

                </div>
            </div>
            
        </div>
    </div>

    

    

    

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/pride-blog/2021/061744938.html">
                    <div class="card-image">
                        
                        
                        <img src="/pride-blog/medias/featureimages/17.jpg" class="responsive-img" alt="Vue源码系列4">
                        
                        <span class="card-title">Vue源码系列4</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2021-06-17
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/pride-blog/categories/Vue%E6%BA%90%E7%A0%81/" class="post-category">
                                    Vue源码
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/pride-blog/tags/Vue%E6%BA%90%E7%A0%81/">
                        <span class="chip bg-color">Vue源码</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/pride-blog/2021/061142501.html">
                    <div class="card-image">
                        
                        
                        <img src="/pride-blog/medias/featureimages/4.jpg" class="responsive-img" alt="Vuex的单一状态管理树">
                        
                        <span class="card-title">Vuex的单一状态管理树</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2021-06-11
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/pride-blog/categories/%E6%AF%8F%E5%A4%A9%E4%B8%80%E4%B8%AA%E7%A6%BB%E8%81%8C%E5%B0%8F%E6%8A%80%E5%B7%A7/" class="post-category">
                                    每天一个离职小技巧
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/pride-blog/tags/log/">
                        <span class="chip bg-color">log</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>



<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/pride-blog/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="/pride-blog/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="/pride-blog/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/pride-blog/libs/codeBlock/codeShrink.js"></script>


    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget card" style="background-color: white;">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/pride-blog/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('0'),
            headingSelector: 'h2, h3, h4'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h2, h3, h4').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>




    <footer class="page-footer bg-color">
    

    <div class="container row center-align"
         style="margin-bottom: 0px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            
                <span id="year">2021-2023</span>
            
            <span id="year">2021</span>
            <a href="/pride-blog/about" target="_blank">zihao xiong</a>
            |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;<a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>
            
            
            
            
            
            <br>

            <!-- 运行天数提醒. -->
            
            <br>
            
        </div>
        <div class="col s12 m4 l4 social-link ">














</div>
    </div>
</footer>

<div class="progress-bar"></div>


    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var searchFunc = function (path, search_id, content_id) {
        'use strict';
        $.ajax({
            url: path,
            dataType: "xml",
            success: function (xmlResponse) {
                // get the contents from search data
                var datas = $("entry", xmlResponse).map(function () {
                    return {
                        title: $("title", this).text(),
                        content: $("content", this).text(),
                        url: $("url", this).text()
                    };
                }).get();
                var $input = document.getElementById(search_id);
                var $resultContent = document.getElementById(content_id);
                $input.addEventListener('input', function () {
                    var str = '<ul class=\"search-result-list\">';
                    var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                    $resultContent.innerHTML = "";
                    if (this.value.trim().length <= 0) {
                        return;
                    }
                    // perform local searching
                    datas.forEach(function (data) {
                        var isMatch = true;
                        var data_title = data.title.trim().toLowerCase();
                        var data_content = data.content.trim().replace(/<[^>]+>/g, "").toLowerCase();
                        var data_url = data.url;
                        data_url = data_url.indexOf('/') === 0 ? data.url : '/' + data_url;
                        var index_title = -1;
                        var index_content = -1;
                        var first_occur = -1;
                        // only match artiles with not empty titles and contents
                        if (data_title !== '' && data_content !== '') {
                            keywords.forEach(function (keyword, i) {
                                index_title = data_title.indexOf(keyword);
                                index_content = data_content.indexOf(keyword);
                                if (index_title < 0 && index_content < 0) {
                                    isMatch = false;
                                } else {
                                    if (index_content < 0) {
                                        index_content = 0;
                                    }
                                    if (i === 0) {
                                        first_occur = index_content;
                                    }
                                }
                            });
                        }
                        // show search results
                        if (isMatch) {
                            str += "<li><a href='" + data_url + "' class='search-result-title'>" + data_title + "</a>";
                            var content = data.content.trim().replace(/<[^>]+>/g, "");
                            if (first_occur >= 0) {
                                // cut out 100 characters
                                var start = first_occur - 20;
                                var end = first_occur + 80;
                                if (start < 0) {
                                    start = 0;
                                }
                                if (start === 0) {
                                    end = 100;
                                }
                                if (end > content.length) {
                                    end = content.length;
                                }
                                var match_content = content.substr(start, end);
                                // highlight all keywords
                                keywords.forEach(function (keyword) {
                                    var regS = new RegExp(keyword, "gi");
                                    match_content = match_content.replace(regS, "<em class=\"search-keyword\">" + keyword + "</em>");
                                });

                                str += "<p class=\"search-result\">" + match_content + "...</p>"
                            }
                            str += "</li>";
                        }
                    });
                    str += "</ul>";
                    $resultContent.innerHTML = str;
                });
            }
        });
    };

    searchFunc('/pride-blog/search.xml', 'searchInput', 'searchResult');
});
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="/pride-blog/libs/materialize/materialize.min.js"></script>
    <script src="/pride-blog/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/pride-blog/libs/aos/aos.js"></script>
    <script src="/pride-blog/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/pride-blog/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/pride-blog/js/matery.js"></script>

    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    <script src="/pride-blog/libs/others/clicklove.js" async="async"></script>
    
    

    

    

    <!--腾讯兔小巢-->
    
    
    <script type="text/javascript" color="0,0,255"
        pointColor="0,0,255" opacity='0.7'
        zIndex="-1" count="99"
        src="/pride-blog/libs/background/canvas-nest.js"></script>
    

    
    
    <script type="text/javascript" size="150" alpha='0.6'
        zIndex="-1" src="/pride-blog/libs/background/ribbon-refresh.min.js" async="async"></script>
    

    
    <script type="text/javascript" src="/pride-blog/libs/background/ribbon-dynamic.js" async="async"></script>
    

    
    <script src="/pride-blog/libs/instantpage/instantpage.js" type="module"></script><!-- hexo-inject:begin --><!-- hexo-inject:end -->
    

</body>
<script src="./../source/js/cursor.js"></script>

</html>
